﻿#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import ftdi
import math

class LedMatrix:
	### ユーザーメソッド ###
	# <summary>
	# ピンアサインを設定します。
	# </summary>
	# <param name="clock">クロックピン</param>
	# <param name="enable">イネーブルピン</param>
	# <param name="d0">データピン0</param>
	# <param name="d1">データピン1</param>
	# <param name="d2">データピン2</param>
	# <param name="d3">データピン3</param>
	# <param name="d4">データピン4</param>
	# <param name="d5">データピン5</param>
	def __init__(self, clock, enable, d0, d1, d2, d3, d4, d5):
		## 定数宣言 ##
		# FTDIデバイス #
#		self.FTDI_BAUDRATE = 230400  # bps
		self.FTDI_BAUDRATE = 350000  # bps
		self.FTDI_VENDOR = 0x0403
#		self.FTDI_PRODUCT = 0x6001  # FT232RL
		self.FTDI_PRODUCT = 0x6010  # FT2232D
		self.FTDI_INTERFACE = ftdi.INTERFACE_B

		## 変数に記憶 ##
		self._clock_pin = 1 << clock
		self._enable_pin = 1 << enable

		self._data_pins = ( 1 << d0, 1 << d1, 1 << d2, 1 << d3, 1 << d4, 1 << d5 )

		## コマンドリスト ##
		# クリア #
		self._commandList = []


	# <summary>
	# LEDマトリックスの幅（ピクセル単位）を取得します。
	# </summary>
	def Width(self):
		return self._LedMatrix_Width


	# <summary>
	# LEDマトリックスの高さ（ピクセル単位）を取得します。
	# </summary>
	def Height(self):
		return self._LedMatrix_Height


	# <summary>
	# デバイスを指定された解像度で初期化します。
	# </summary>
	# <param name="width">表示サイズの幅を設定</param>
	# <param name="height">表示サイズの高さを設定</param>
	def Begin(self, width, height):
		## 変数に記憶 ##
		self._LedMatrix_Width = width
		self._LedMatrix_Height = height

		## FTDIデバイスを初期化 ##
		# インスタンス作成 #
		self._FTDI_Device = ftdi.ftdi_new()

		# インターフェース設定 #
		self._checkStatus(ftdi.ftdi_set_interface(self._FTDI_Device, self.FTDI_INTERFACE))

		# 接続 #
		self._checkStatus(ftdi.ftdi_usb_open(self._FTDI_Device, self.FTDI_VENDOR, self.FTDI_PRODUCT))

		# モード設定 #
		port = (self._clock_pin |
			self._enable_pin |
			self._data_pins[0] |
			self._data_pins[1] |
			self._data_pins[2] |
			self._data_pins[3] |
			self._data_pins[4] |
			self._data_pins[5])
		self._checkStatus(ftdi.ftdi_enable_bitbang(self._FTDI_Device, port))

		# ボーレート設定 #
		self._checkStatus(ftdi.ftdi_set_baudrate(self._FTDI_Device, self.FTDI_BAUDRATE))

		## LEDマトリックスの初期化 ##
		# リセット #
		self._addressReset()

		# 処理を実行 #
		self.Sync()


	# <summary>
	# デバイスとの接続を切断します。
	# </summary>
	def End(self):
		try:
			# コマンドリスト クリア #
			self._commandList = []
	
			# 表示をクリア #
			self.Clear()
	
			# 処理を実行 #
			self.Sync()
	
			# FTDIデバイスとの接続を切断 #
			self._checkStatus(ftdi.ftdi_usb_close(self._FTDI_Device))
			ftdi.ftdi_deinit(self._FTDI_Device)

		except AttributeError:
			pass
	

	# <summary>
	# コマンドの文字列を返します。
	# </summary>
	def ToString(self):
		return "".join(map(chr, self._commandList))


	# <summary>
	# コマンドのリストを返します。
	# </summary>
	def ToList(self):
		return self._commandList


	# <summary>
	# コマンドのタプルを返します。
	# </summary>
	def ToTuple(self):
		return tuple(self._commandList)


	# <summary>
	# バッファリングされたデータを転送します。
	# </summary>
	def Sync(self):
		# データを転送 #
		if len(self._commandList) == 0:
			return

		binLine = "".join(map(chr, self._commandList))
		ftdi.ftdi_write_data(self._FTDI_Device, binLine, len(binLine))
		self._commandList = []


	# <summary>
	# 制御コードを転送します。
	# </summary>
	def Write(self, data):
		ftdi.ftdi_write_data(self._FTDI_Device, data, len(data))
		

	# <summary>
	# バッファリングされたデータを転送します。
	# </summary>
	def Write(self, data):
		ftdi.ftdi_write_data(self._FTDI_Device, data, len(data))


	# <summary>
	# バッファをクリアします。
	# </summary>
	def Reset(self):
		self._commandList = []


	# <summary>
	# 表示をクリアします。
	# </summary>
	# <param name="sync">即時反映</param>
	def Clear(self, sync = False):
		# アドレスリセット #
		self._addressReset()

		# データを送信 #
		for i in range(self._LedMatrix_Width * self._LedMatrix_Height):
			self._commandList.append(self._enable_pin)
			self._commandList.append(self._enable_pin | self._clock_pin)

		# コマンドを送信 #
		if sync:
			self.Sync()


	# <summary>
	# ビットマップを描画
	# </summary>
	# <param name="array">色の数値配列</param>
	# <param name="sync">即時反映</param>
	def Draw(self, data, x = 0, y = 0, width = 0, height = 0, sync = False):
		# アドレスリセット #
		self._addressReset()

		# データを送信 #
		for i in range(0, len(data), 3):
			r = ord(data[i + 0])
			g = ord(data[i + 1])
			b = ord(data[i + 2])

			if r > 128 and g > 64 and g <= 192 and b <= 128:
				color = 0x0F

			elif r > 128 and g <= 128 and b <= 128:
				color = 0x03

			elif r <= 128 and g > 64 and b <= 128:
				color = 0x0C

			else:
				color = 0x00

			self._commandList.append(self._data2pin(color & 0x3F) | self._enable_pin)
			self._commandList.append(self._data2pin(color & 0x3F) | self._enable_pin | self._clock_pin)

		# コマンドを送信 #
		if sync:
			self.Sync()


	### クラス内メソッド ###
#	def _cmd8bits(self, value):
#		self.commandAdd(value, False)
#
#	def _data8bits(self, value):
#		self.commandAdd(value, True)
#
#	def _commandAdd(self, value, rs, dataShift)
#		 データ セット 
#		dataUpper = _data2pin((value >> 4) & 0x0F)
#		dataLower = _data2pin(value & 0x0F)
#
#		 コマンド追加 
#		if rs:
#			# データモード
#			self._commandList.append(dataUpper | self._a0_pin | self._reset_pin | self._enable_pin)
#			self._commandList.append(dataUpper | self._a0_pin | self._reset_pin | self._enable_pin | self._latch_pin)
#
#			self._commandList.append(dataLower | self._a0_pin | self._reset_pin | self._enable_pin)
#			self._commandList.append(dataLower | self._a0_pin | self._reset_pin)
#			self._commandList.append(dataLower | self._a0_pin | self._reset_pin | self._enable_pin)
#
#			# コマンドモード
#			self._commandList.append(dataUpper | self._reset_pin | self._enable_pin)
#			self._commandList.append(dataUpper | self._reset_pin | self._enable_pin | self._latch_pin)
#
#			self._commandList.append(dataLower | self._reset_pin | self._enable_pin)
#			self._commandList.append(dataLower | self._reset_pin))
#			self._commandList.append(dataLower | self._reset_pin | self._enable_pin)
#
#
#	def _commandDelay(self, microseconds):
#		if len(self._commandList) == 0:
#			return
#	
#		lastCommand = self._commandList[len(self._commandList) - 1]
#
#		nopCount = int(microseconds * self.FTDI_BAUDRATE / math.pow(10, 5))   補正値
#		nopCount = int(microseconds * self.FTDI_BAUDRATE / math.pow(10, 6))   計算値
#
#		for i in range(nopCount):
#			self._commandList.append(lastCommand)


	def _data2pin(self, data):
		# データをピンに変換 #
		pinData = 0

		for i in range(len(self._data_pins)):
			if ((data >> i) & 0x01) == 1:
				pinData |= self._data_pins[i]

		return pinData


	def _addressReset(self):
		# アドレスリセット #
		self._commandList.append(0x00)
		self._commandList.append(self._clock_pin)
		self._commandList.append(0x00)


	def _checkStatus(self, status):
#		if status == FTDI.FT_STATUS.FT_OK:
		if status == 0:
			return

#		elif status == FTDI.FT_STATUS.FT_DEVICE_NOT_FOUND:
#			throw new USB_IOException("デバイスが見つかりません。")
#
#		elif status == FTDI.FT_STATUS.FT_DEVICE_NOT_OPENED:
#			throw new USB_IOException("デバイスと接続されていません。")
#
#		elif status == FTDI.FT_STATUS.FT_DEVICE_NOT_OPENED_FOR_ERASE:
#			throw new USB_IOException("削除のためにデバイスと接続されていません。")
#
#		elif status == FTDI.FT_STATUS.FT_DEVICE_NOT_OPENED_FOR_WRITE:
#			throw new USB_IOException("書き込むためにデバイスと接続されていません。")
#
#		elif status == FTDI.FT_STATUS.FT_EEPROM_ERASE_FAILED:
#			throw new USB_IOException("EEPROMの削除に失敗しました。")
#
#		elif status == FTDI.FT_STATUS.FT_EEPROM_NOT_PRESENT:
#			throw new USB_IOException("EEPROMが見つかりません。")
#
#		elif status == FTDI.FT_STATUS.FT_EEPROM_NOT_PROGRAMMED:
#			throw new USB_IOException("EEPROMがプログラムされていません。")
#
#		elif status == FTDI.FT_STATUS.FT_EEPROM_READ_FAILED:
#			throw new USB_IOException("EEPROMの読み込みに失敗しました。")
#
#		elif status == FTDI.FT_STATUS.FT_EEPROM_WRITE_FAILED:
#			throw new USB_IOException("EEPROMの書き込みに失敗しました。")
#
#		elif status == FTDI.FT_STATUS.FT_FAILED_TO_WRITE_DEVICE:
#			throw new USB_IOException("データ転送に失敗しました。")
#
#		elif status == FTDI.FT_STATUS.FT_INSUFFICIENT_RESOURCES:
#			throw new USB_IOException("リソースが不足しています。")
#
#		elif status == FTDI.FT_STATUS.FT_INVALID_ARGS:
#			throw new USB_IOException("引数が無効です。")
#
#		elif status == FTDI.FT_STATUS.FT_INVALID_BAUD_RATE:
#			throw new USB_IOException("ボーレートが無効です。")
#
#		elif status == FTDI.FT_STATUS.FT_INVALID_HANDLE:
#			throw new USB_IOException("ハンドルが無効です。")
#
#		elif status == FTDI.FT_STATUS.FT_INVALID_PARAMETER:
#			throw new USB_IOException("パラメーターが無効です。")
#
#		elif status == FTDI.FT_STATUS.FT_IO_ERROR:
#			throw new USB_IOException("I/Oエラーが発生しました。")
#
#		else:
#			throw new USB_IOException("不明なエラーが発生しました。")
#
